package luzhin;

import java.io.*;
import java.util.*;
import org.apache.log4j.Logger;

/**
 * Game.
 * @author lacungus
 *
 */
public class Game extends Observable implements Serializable, Runnable {
    private static Logger logger = Logger.getLogger(Game.class);

	private static final long serialVersionUID = 3489239832L;
	private Board board;
	private Strategy whiteStrategy;
	private Strategy blackStrategy;
	private MoveValidator moveValidator;
	private MateValidator mateValidator;

	public static int WHITE_WON = 1;
	public static int BLACK_WON = -1;
	public static int DRAW = 0;
	private boolean isOver = false;
	private int winner = 0;
	
	public void setWhiteStrategy(Strategy strategy) {
		this.whiteStrategy = strategy;
	}

	public void setBlackStrategy(Strategy strategy) {
		this.blackStrategy = strategy;
	}

	/**
	 * 
	 * @return Current board.
	 */
	public Board getBoard() {
		return board;
	}
	
	/**
	 * 
	 * @return true if the game is over.
	 */	
	public boolean isOver() {
		return isOver;
	}
	
	/**
	 * can be called only if isOver have already been called.
	 * @return winner 
	 */
	public int getWinner() {
		return winner;
	}

	/**
	 * Run a game.
	 * @param whiteStrategy
	 * @param blackStrategy
	 */
	public Game(Strategy whiteStrategy, Strategy blackStrategy) {		
		board = new Board();
		this.whiteStrategy = whiteStrategy; 
		this.blackStrategy = blackStrategy;
		
		moveValidator = new AdvancedMoveValidator();
		mateValidator = new MateValidator();
	}
	
	/**
	 * Save the game.
	 * @param path Path to the file to save.
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public void save(String path) throws FileNotFoundException, IOException {
	    FileOutputStream fos = new FileOutputStream(path);
	    ObjectOutputStream outStream = new ObjectOutputStream(fos);
	    outStream.writeObject(this.board);
	    outStream.flush();	        
//	    System.out.println("Game saved.");
	}
	
	public void setBoard(Board board) {
		this.board = board;
	}
	
	private Game() {
		moveValidator = new AdvancedMoveValidator();
		mateValidator = new MateValidator();		
	}
	
	/**
	 * Load the game.
	 * @param path 
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static Game load(String path) throws FileNotFoundException, IOException {
		FileInputStream fis = new FileInputStream(path);
        ObjectInputStream inStream = new ObjectInputStream(fis);
        
	    Board board = null;
	    try {
	    	board = (Board)inStream.readObject();
	    } catch (Exception e) {
	    	logger.error("Error", e);
	    }
	    Game game = new Game();
	    game.setBoard(board);
	    return game;
	}
	
	/**
	 * Starts the game.
	 */
	public void run() {
		try {
			boolean whiteToMove = true;
			while (true) {
				Move move = null;
				if (whiteToMove) {
					move = whiteStrategy.makeMove(board);					
				} else {
					move = blackStrategy.makeMove(board);					
				}
				if (move == null) {
					if (whiteToMove) 
						winner = BLACK_WON;
					else
						winner = WHITE_WON;
					isOver = true;
					this.setChanged();
					this.notifyObservers();
					break;
				}
				if (!moveValidator.isValid(board, move)) {
					System.out.println("INVALID MOVE!");
					this.setChanged();
					this.notifyObservers();
					continue;
				}
				board = board.applyMove(move);
				mateValidator.process(this);
				if (mateValidator.isOver()) {
					winner = mateValidator.getWinner(); 
					isOver = true;
					this.setChanged();
					this.notifyObservers();
					break;					
				}
				whiteToMove = !whiteToMove;
				// notify the observer that a move have been done.
				this.setChanged();
				this.notifyObservers();
				
			}
		} catch (StrategyException e) {
			logger.error("Error: " + e);
		}
	}
}
